package cn.lanqiao.springboot.interceptor;

import cn.lanqiao.springboot.entity.Menu;
import cn.lanqiao.springboot.service.MenuService;
import cn.lanqiao.springboot.util.CurrentUserUtil;
import cn.lanqiao.springboot.util.Result;
import cn.lanqiao.springboot.vo.UserVO;
import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTVerifier;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.exceptions.JWTDecodeException;
import com.auth0.jwt.exceptions.SignatureVerificationException;
import com.auth0.jwt.exceptions.TokenExpiredException;
import com.auth0.jwt.interfaces.DecodedJWT;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.List;

/**
 * 方法执行顺序为 preHandle – Controller 方法 – postHandle – afterCompletion
 * preHandle: 访问controller之前调用
 * postHandle: ModelAndView渲染前调用
 * afterCompletion: 完成请求和响应后调用
 * ps:需要配置config
 */
@Component
public class LoginInterceptor implements HandlerInterceptor {

    /**
     * 在yaml配置文件中读取秘钥
     */
    @Value("${JWT.key}")
    private String key;

    @Autowired
    MenuService menuService;

    /**
     * 使用拦截器实现JWT登录验证
     */
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        try {
            //获得token操作
            String token = request.getHeader("Authorization");
            if (token == null) {
                return failResult(611, "请先登录！！", response);
            }
            //解密操作
            Algorithm algorithm = Algorithm.HMAC256(key);//设置加密方式
            JWTVerifier jwtVerifier = JWT.require(algorithm).build();//创建认证对象，创建失败会抛出AlgorithmMismatchException异常。
            DecodedJWT decodedJWT = jwtVerifier.verify(token.substring(7));//对去掉头信息的token进行验证
            //获得用户数据
            UserVO userVO = new UserVO();
            userVO.setUserId(decodedJWT.getClaim("userId").asInt());//获得JWT中的用户id
            userVO.setUserName(decodedJWT.getClaim("userName").asString());//获得JWT中的用户名
            userVO.setRoleName(decodedJWT.getClaim("roleName").asString());//获得JWT中的角
            userVO.setRoleId(decodedJWT.getClaim("roleId").asInt());
            CurrentUserUtil.set(userVO);
            //鉴权操作
            //1、获得浏览器请求地址 /user/list
            String url = request.getRequestURI();
            if(url.equals("/menu/list")){
                return true;
            }
            //2、roleId获得权限集合 roleId - List<Url>
            List<Menu> menuList = menuService.getList(decodedJWT.getClaim("roleId").asInt());
            //3、请求地址是否在权限集合里
            for(Menu menu : menuList){
                if(menu.getAuthorize() != null){
                    if(url.matches(menu.getAuthorize())){
                        return true;
                    }
                }
            }
            return failResult(600, "权限不足！", response);
        } catch (JWTDecodeException e) {
            return failResult(612, "token格式错误！", response);
        } catch (SignatureVerificationException e) {
            return failResult(613, "token解密失败！", response);
        } catch (TokenExpiredException e) {
            return failResult(614, "token过期，请重新登录！", response);
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 验证失败统一向前端返回错误结果
     *
     * @param code     错误码
     * @param message  错误信息
     * @param response 响应对象
     */
    public boolean failResult(int code, String message, HttpServletResponse response) {
        try {
            ObjectMapper objectMapper = new ObjectMapper();//使用jackson，也可以使用fastjson
            response.setContentType("text/html;charset=UTF-8");
            response.getWriter().print(objectMapper.writeValueAsString(Result.error(code, message)));
        } catch (IOException e) {
            e.printStackTrace();
        }
        return false;
    }
}
